package
{
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	import mx.containers.Grid;
	import mx.containers.GridItem;
	import mx.containers.GridRow;
	import mx.controls.Button;
	import mx.core.IMXMLObject;
	import mx.events.FlexEvent;
	import mx.events.NumericStepperEvent;
	import mx.events.StateChangeEvent;
	import mx.managers.PopUpManager;

	public class Controller implements IMXMLObject
	{
		[Embed(source = "flag.png")]
		private var flag:Class;

		[Embed(source = "bomb.png")]
		private var bomb:Class;

		[Embed(source = "cross.png")]
		private var cross:Class;
		
		private var view:fxug1;
		private var timer:Timer;
		private var started:Date;
		private var opened:uint;
		
		/**
		 * Gridを組み立て、ボタンを配置します。
		 */
		private function assembleGrid(event:NumericStepperEvent = null):void {
			var cols:int = this.view.cols.value;
			var rows:int = this.view.rows.value;
			
			var currentBombs:uint = this.view.bombs.value;
			var maxBombs:uint = cols * rows - 1;
			if (currentBombs > maxBombs) {
				this.view.bombs.value = maxBombs;
			}

			var grid:Grid = this.view.grid;
			grid.removeAllChildren();
			for (var i:int = 0; i < rows; i++) {
				var gridRow:GridRow = new GridRow();

				for (var j:int = 0; j < cols; j++) {
					var button:Button = new Button();
					button.width = 18;
					button.height = 18;
					button.data = {count : 0, isBomb : false, isMarked : false, x : j, y : i};
					button.addEventListener(MouseEvent.CLICK, this.button_Click);
					button.styleName = "bombButton";

					var gridItem:GridItem = new GridItem();
					gridItem.addChild(button);
					gridRow.addChild(gridItem);
				}
				grid.addChild(gridRow);
			}

			this.setBombs();
		}

		private function setBombs():void {
			// ランダムにボタンを選んで爆弾に設定する
			var cols:int = this.view.cols.value;
			var rows:int = this.view.rows.value;
			var bombs:int = this.view.bombs.value;
			var grid:Grid = this.view.grid;
			var grids:int = this.view.cols.value * this.view.rows.value;

			this.view.restBombs.text = bombs.toString();

			var i:int = 0;
			do {
				// 0 <= index < マス目の数
				var index:uint = Math.floor(Math.random() * grids);
				var c:uint;
				var r:uint;
				if (index > 0) {
					c = index % cols;
					r = Math.floor(index / cols);
				} else {
					c = 0;
					r = 0;
				}

				var gridRow:GridRow = grid.getChildAt(r) as GridRow;
				var gridItem:GridItem = gridRow.getChildAt(c) as GridItem;
				var button:Button = gridItem.getChildAt(0) as Button;
				if (!button.data.isBomb) {
					// 爆弾決定
					button.data.isBomb = true;
					i++;

					// 周りのマス目のcountをインクリ
					this.increment(this.getAroundButtons(c, r));
				}
			} while (i < bombs);
		}

		private function getAroundButtons(x:uint, y:uint):Array {
			var positions:Array = [];

			// 左右のボタンの位置を追加
			// 左
			if (x > 0) {
				positions.push({x : x - 1, y : y});
			}
			// 右
			if (x < this.view.cols.value - 1) {
				positions.push({x : x + 1, y : y});
			}

			// 一番上の行でないときは上のボタンの位置を追加
			if (y > 0) {
				// 真上
				positions.push({x : x, y : y - 1});

				// 右上
				if (x < this.view.cols.value - 1) {
					positions.push({x : x + 1, y : y - 1});
				}

				// 左上
				if (x > 0) {
					positions.push({x : x - 1, y : y - 1});
				}
			}

			// 一番下の行でないときは下のボタンの位置を追加
			if (y < this.view.rows.value - 1) {
				// 真下
				positions.push({x : x, y : y + 1});

				// 右下
				if (x < this.view.cols.value - 1) {
					positions.push({x : x + 1, y : y + 1});
				}

				// 左下
				if (x > 0) {
					positions.push({x : x - 1, y : y + 1});
				}
			}

			return positions;
		}

		/**
		 * 真上のマスから時計回りに、周りにいくつ爆弾があるかを示す値をインクリする。
		 */
		private function increment(positions:Array):void {
			// ボタンを探し出してインクリ
			for (var key:Object in positions) {
				var button:Button = this.getButton(positions[key]);
				button.data.count++;
			}
		}

		private function getButton(position:Object):Button {
			var grid:Grid = this.view.grid;
			var gridRow:GridRow = grid.getChildAt(position.y) as GridRow;
			return (gridRow.getChildAt(position.x) as GridItem).getChildAt(0) as Button;
		}

		private function open(b:Object):void {
			var button:Button;
			if (b is Button) {
				button = b as Button;

				// 旗が立っていれば何もしない
				if (button.data.isMarked) {
					return;
				}

				button.enabled = false;
				if (button.data.isBomb) {
					// ゲームオーバー

					this.view.currentState = State.Stop;
					button.setStyle("borderColor", 0xff0000);
					button.setStyle("icon", this.bomb);
				} else {
					// セーフ

					this.opened++;

					// 0なら自動的に周りのボタンをクリックする
					if (button.data.count == 0) {
						this.open(this.getAroundButtons(button.data.x, button.data.y));
					} else {
						if (this.opened == this.view.cols.value * this.view.rows.value - this.view.bombs.value) {
							this.view.currentState = State.Clear;
							return;
						}

						button.label = button.data.count;
					}
				}
			} else if (b is Array) {
				for (var key:Object in b) {
					button = this.getButton(b[key]);
					if (button.enabled) {
						this.open(button);
					}
				}
			}
		}

		private function showBombs():void {
			var cols:uint = this.view.cols.value;
			var rows:uint = this.view.rows.value;

			for (var i:uint = 1; i < cols * rows; i++) {
				var x:uint = i % cols;
				var y:uint = Math.floor(i / cols);
				var positions:Array = this.getAroundButtons(x, y);

				for (var key:Object in positions) {
					var button:Button = this.getButton(positions[key]);

					if (!button.enabled || !button.data.isBomb) {
						if (button.data.isMarked) {
							// 間違ってるよ
							button.setStyle("icon", this.cross);
						}
						continue;
					}

					// ここが爆弾でした
					button.setStyle("icon", this.bomb);
				}
			}
		}

		public function initialized(document:Object, id:String):void {
			this.view = document as fxug1;
			this.view.addEventListener(FlexEvent.CREATION_COMPLETE, this.creationComplete);
		}

		private function creationComplete(event:FlexEvent):void {
			this.view.currentState = State.Ready;
			this.assembleGrid();
			
			// タイマー
			this.timer = new Timer(10);
			this.timer.addEventListener(TimerEvent.TIMER, this.timer_Timer);

			// イベントハンドラ
			this.view.addEventListener(StateChangeEvent.CURRENT_STATE_CHANGE, this.state_Change);
			this.view.cols.addEventListener(NumericStepperEvent.CHANGE, this.assembleGrid);
			this.view.rows.addEventListener(NumericStepperEvent.CHANGE, this.assembleGrid);
			this.view.bombs.addEventListener(NumericStepperEvent.CHANGE, this.assembleGrid);
			this.view.resetButton.addEventListener(MouseEvent.CLICK, this.resetButton_Click);
		}

		private function timer_Timer(event:TimerEvent):void {
			this.displayElapsedTime();
		}

		private function displayElapsedTime():String {
			var time:uint = new Date().getTime() - this.started.getTime();
			
			var timeString:String = time.toString().replace(/(\d+)(\d{3})/, "$1.$2");
			this.view.timeLabel.text = timeString;
			return timeString;
		}

		/**
		 * ボタンのクリックイベントハンドラ。
		 * Shiftキーを押しながらクリックすると旗が立つ。
		 * 旗が立てられたボタンはクリックできない。
		 */
		private function button_Click(event:MouseEvent):void {
			switch (this.view.currentState) {
				case State.Ready:
					this.view.currentState = State.Playing;
					break;
				case State.Playing:
					break;
				default:
					return;
			}

			var target:Button = event.target as Button;

			if (event.shiftKey) {
				// shift キーが押されていた場合

				var restBombs:uint = Number(this.view.restBombs.text);

				if (target.data.isMarked) {
					// 既に旗が立っている場合

					target.setStyle("icon", null);
					target.data.isMarked = false;
					this.view.restBombs.text = (++restBombs).toString();
				} else {
					// 旗が立っていない場合

					if (restBombs < 1) {
						return;
					}

					target.setStyle("icon", this.flag);
					target.data.isMarked = true;
					this.view.restBombs.text = (--restBombs).toString();
				}
			} else {
				this.open(target);
			}
		}

		private function resetButton_Click(event:MouseEvent):void {
			this.timer.stop();
			this.view.timeLabel.text = "0";
			this.view.restBombs.text = this.view.bombs.value.toString();
			this.assembleGrid();
			this.view.currentState = State.Ready;
		}

		private function state_Change(event:StateChangeEvent):void {
			switch (event.newState) {
				case State.Ready:
					this.view.messageLabel.text = "";
					break;
				case State.Playing:
					this.started = new Date();
					this.timer.start();
					this.opened = 0;
					break;
				case State.Stop:
					this.timer.stop();
					this.displayElapsedTime();
					this.showBombs();
					this.view.messageLabel.text = "Game Over...";
					break;
				case State.Clear:
					this.timer.stop();
					this.view.messageLabel.text = "Good Job!!";
					var window:NewRecordWindow = PopUpManager.createPopUp(view, NewRecordWindow, true) as NewRecordWindow;
					window.time = this.displayElapsedTime();
					PopUpManager.centerPopUp(window);
					break;
			}
		}
	}
}
